// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OrderService.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Reflection;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Events;
    using Org.OpenTrader.Framework.Forge.DataCache;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;
    using Org.OpenTrader.Framework.LiveObjects;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// This is an implementation of IOrderService with security support (using some ISecurityChecker)
    /// </summary>
    [Credits("Order Service", "Handles order routing operations", "David Denis", "Copyright (C) David Denis <mailto:david.denis@systemathics.com>")]
    [ImplementsInterface("Org.OpenTrader.Framework.Forge.Interfaces.IOrderService")]
    public sealed partial class OrderService : Service, IOrderService
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(MethodBase.GetCurrentMethod());

        /// <summary>
        /// The yCells.
        /// </summary>
        private IDictionary<Guid, YCell> yCells;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="OrderService"/> class.
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        public OrderService(ILiveObjectDescriptor descriptor, ILiveObjectContainer container)
            : base(descriptor, container)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets Wires.
        /// </summary>
        public IWire[] Wires { get; set; }

        #endregion

        #region Implemented Interfaces

        #region ILiveObject

        /// <summary>
        /// The on activation context.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="activationEventArgs">
        /// The activation Context.
        /// </param>
        public override void OnActivation(object sender, ActivationEventArgs activationEventArgs)
        {
            if (activationEventArgs is ActivationEventArgs.CreateDestroy)
            {
                switch (activationEventArgs.Type)
                {
                    case EActivationType.Activation:

                        // Create a DBBackOffice (with same ID) entry if not already there. Used to store things.
                        if (!Singleton<DC>.Instance.ExistsOrderService(this.Descriptor.ID))
                        {
                            Singleton<DC>.Instance.CreateOrderService(this.Descriptor.ID);
                        }

                        // Cells used to track orders
                        this.yCells = new Dictionary<Guid, YCell>();

                        // Setup available wires
                        this.Wires = new IWire[this.Plugins.Length];
                        var n = 0;
                        foreach (var v in this.Plugins)
                        {
                            var orderPlugin = v as IOrderPlugin;
                            if (orderPlugin != null)
                            {
                                // Wires
                                var wire = new Wire
                                    {
                                        PluginID = v.Descriptor.ID, 
                                        PluginName = v.Descriptor.Name, 
                                        ObjectConfigurationSchema =
                                            this.Container.GetConfigurationSchema(
                                            (orderPlugin.Descriptor as LiveObjectDescriptor.Local).ClassType, orderPlugin.OrderRoutingConfigurationSchema)
                                    };
                                this.Wires[n++] = wire;

                                // Track connection events
                                orderPlugin.ConnectionEventCB += this.OnConnectionEvent;
                            }
                            else
                            {
                                // TODO : Logging
                            }
                        }

                        break;

                    case EActivationType.Desactivation:
                        foreach (var v in this.Plugins)
                        {
                            var orderPlugin = v as IOrderPlugin;
                            if (orderPlugin != null)
                            {
                                // No more connection events tracking
                                orderPlugin.ConnectionEventCB -= this.OnConnectionEvent;
                            }
                        }

                        break;
                }
            }
        }

        #endregion

        #region IOrderProvider

        /// <summary>
        /// The cancel order.
        /// </summary>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <returns>
        /// The cancel order.
        /// </returns>
        public Status CancelOrder(IOrder order)
        {
            var v1 = Timings.BenchmarkNow;

            // This is not an order created here
            if (!this.yCells.ContainsKey(order.ID))
            {
                return Status.Failure(Identity, "CancelOrder: This order is not known here. Cannot cancel");
            }

            // Check security
            var securitystatus = SecurityChecker.CheckOrderCancelRequest(order);
            if (!securitystatus.Sucess)
            {
                // Failure while initiating "Cancel" request
                return Status.Failure(Identity, "CancelOrder: Security service denied operation", securitystatus);
            }

            // Front to back
            Front2Back.AddOrderStatus("Cancelling", order);

            // Get the needed wiring from cache
            var wiring = Singleton<DC>.Instance.GetInstrumentStaticDataFromCache(order.InstrumentID).OrderWiring;

            // Initiate "Cancel" request on wired plug-in
            var cancelstatus = (this[wiring.PluginID] as IOrderPlugin).Cancel(wiring, order, this.yCells[order.ID].Input);

            Timings.Benchmark("CancelOrder", v1);

            // Successfully initiated "Cancel" request
            return cancelstatus;
        }

        /// <summary>
        /// The create order.
        /// </summary>
        /// <param name="instrumentID">
        /// The instrumentID.
        /// </param>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="way">
        /// The way.
        /// </param>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <param name="quantity">
        /// The quantity.
        /// </param>
        /// <param name="bookingPath">
        /// The bookingPath.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        /// <returns>
        /// </returns>
        public Status<IOrder> CreateOrder(
            Guid instrumentID, EOrderType type, EOrderWay way, double price, int quantity, string bookingPath, EventHandler<OrderEventArgs> handler)
        {
            IOrderWiring wiring;

            var v0 = Timings.BenchmarkNow;

            var v1 = Timings.BenchmarkNow;
            {
                // Clear this path
                bookingPath = PathUtil.Cleanup(bookingPath);
            }

            Timings.Benchmark("CreateOrder - Booking path cleanup", v1);

            var v2 = Timings.BenchmarkNow;
            {
                // Check security
                var securitystatus = SecurityChecker.CheckOrderCreateRequest(instrumentID, type, way, price, quantity, bookingPath);
                if (!securitystatus.Sucess)
                {
                    // Failure while initiating "Cancel" request
                    return Status.Failure<IOrder>(Identity, "CreateOrder: Security service denied operation", securitystatus);
                }
            }

            Timings.Benchmark("CreateOrder - Security check", v2);

            var v3 = Timings.BenchmarkNow;
            {
                // Get the needed wiring from cache
                wiring = Singleton<DC>.Instance.GetInstrumentStaticDataFromCache(instrumentID).OrderWiring;
            }

            Timings.Benchmark("CreateOrder - Find Wiring", v3);

            // Create the order
            IOrder order = new Order(this.Descriptor.ID, instrumentID, type, way, price, quantity, bookingPath);

            var v4 = Timings.BenchmarkNow;
            {
                // Keep track of order and related resources
                var cell = new YCell(this.yCells, this[wiring.PluginID] as IOrderPlugin, order, handler);
                lock (this.yCells)
                {
                    this.yCells[order.ID] = cell;
                }
            }

            Timings.Benchmark("CreateOrder - Create Tracking cell", v4);

            var v5 = Timings.BenchmarkNow;
            {
                // Front to back
                Front2Back.AddOrderStatus("Creating", order);
            }

            Timings.Benchmark("CreateOrder - F2B Enqueue", v5);

            Timings.Benchmark("CreateOrder", v0);

            // Done
            return Status<IOrder>.Success(Identity, order);
        }

        /// <summary>
        /// The modify order.
        /// </summary>
        /// <param name="oldOrder">
        /// The oldOrder.
        /// </param>
        /// <param name="newOrder">
        /// The newOrder.
        /// </param>
        /// <returns>
        /// The modify order.
        /// </returns>
        public Status ModifyOrder(IOrder oldOrder, IOrder newOrder)
        {
            // This is not an order created here
            if (!this.yCells.ContainsKey(oldOrder.ID))
            {
                return Status.Failure(Identity, "ModifyOrder: This order is not known here. Cannot cancel");
            }

            var v1 = Timings.BenchmarkNow;
            {
                // Check security
                var securitystatus = SecurityChecker.CheckOrderModifyRequest(oldOrder, newOrder);
                if (!securitystatus.Sucess)
                {
                    // Failure while initiating "Cancel" request
                    return Status.Failure(Identity, "ModifyOrder: Security service denied operation", securitystatus);
                }
            }

            Timings.Benchmark("ModifyOrder - Security check", v1);

            // Front to back
            Front2Back.AddOrderStatus("Modyfying", oldOrder);

            // Get the needed wiring from cache
            var wiring = Singleton<DC>.Instance.GetInstrumentStaticDataFromCache(oldOrder.InstrumentID).OrderWiring;

            // Initiate "Modify" request on wired plug-in
            var modifystatus = (this[wiring.PluginID] as IOrderPlugin).Modify(wiring as IOrderWiring, oldOrder, newOrder, this.yCells[oldOrder.ID].Input);

            // Successfully initiated "Modify" request
            return modifystatus;
        }

        /// <summary>
        /// The send order.
        /// </summary>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <returns>
        /// The send order.
        /// </returns>
        public Status SendOrder(IOrder order)
        {
            IOrderWiring wiring;
            Status sendstatus;

            var v0 = Timings.BenchmarkNow;

            // This is not an order created here
            if (!this.yCells.ContainsKey(order.ID))
            {
                return Status.Failure(Identity, "SendOrder: This order is not known here. Cannot cancel");
            }

            var v1 = Timings.BenchmarkNow;
            {
                // Check security
                var securitystatus = SecurityChecker.CheckOrderSendRequest(order);
                if (!securitystatus.Sucess)
                {
                    // Failure while initiating "Cancel" request
                    return Status.Failure(Identity, "SendOrder: Security service denied operation", securitystatus);
                }
            }

            Timings.Benchmark("SendOrder - Security check", v1);

            var v2 = Timings.BenchmarkNow;
            {
                // Front to back
                Front2Back.AddOrderStatus("Sending", order);
            }

            Timings.Benchmark("SendOrder - F2B Enqueue", v2);

            var v3 = Timings.BenchmarkNow;
            {
                // Get the needed wiring from cache
                wiring = Singleton<DC>.Instance.GetInstrumentStaticDataFromCache(order.InstrumentID).OrderWiring;
            }

            Timings.Benchmark("SendOrder - Find Wiring", v3);

            var v4 = Timings.BenchmarkNow;
            {
                // Initiate "Send" request on wired plug-in
                sendstatus = (this[wiring.PluginID] as IOrderPlugin).Send(wiring, order, this.yCells[order.ID].Input);
            }

            Timings.Benchmark("SendOrder - Plugin Send(...)", v4);

            Timings.Benchmark("SendOrder - Total time", v0);

            // Successfully initiated "Send" request
            return sendstatus;
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The on connection event.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnConnectionEvent(object sender, ConnectionEventArgs args)
        {
            // Forward the connection event to the dispatcher, encapsulating it in FeedEventArgs.ConnectionStatus
            // this.ForwardHandler(this.OrderPlugin, new OrderEventArgs.ConnectionStatus(args));
        }

        #endregion

        /// <summary>
        /// YCell is positionned between the caller of order service (internal side) and the plugin (external side)
        /// It is used to achieve front2back actions before the data is sent to caller
        /// here is the workflow
        /// 
        /// event received on Input -&gt; Enqueue status or executions to Front2Back -&gt; Forward to output
        /// [From plugin]              [inside cell code]                            [to service user]
        /// 
        /// An OutputReport message will make the cell leave the service cell store 
        /// </summary>
        internal class YCell
        {
            #region Constants and Fields

            /// <summary>
            /// The cells.
            /// </summary>
            private readonly IDictionary<Guid, YCell> cells;

            /// <summary>
            /// Gets or sets order.
            /// </summary>
            private readonly IOrder order;

            /// <summary>
            /// Gets or sets output.
            /// </summary>
            private readonly EventHandler<OrderEventArgs> output;

            /// <summary>
            /// The plugin.
            /// </summary>
            private readonly IOrderPlugin plugin;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="YCell"/> class.
            /// </summary>
            /// <param name="cells">
            /// The cells.
            /// </param>
            /// <param name="plugin">
            /// The plugin.
            /// </param>
            /// <param name="order">
            /// The order.
            /// </param>
            /// <param name="output">
            /// The output.
            /// </param>
            public YCell(IDictionary<Guid, YCell> cells, IOrderPlugin plugin, IOrder order, EventHandler<OrderEventArgs> output)
            {
                // The dictionary where this YCell is stored (we need it to remove the cell when outreport is received)
                this.cells = cells;

                // The orderfor which we're waiting for events on Input
                this.order = order;

                // forward input events to output (after doing the front2back)
                this.output = output;

                // The plugin which will delived us the events on Input
                this.plugin = plugin;

                // input (to be passed to the plugin when create/send/modify)
                this.Input = new RemotableEventHandler<OrderEventArgs>("order-" + order.ID);
                this.Input += this.OnOrderEvent;

                // subcribe to plugin connection events
                this.plugin.ConnectionEventCB += this.OnConnectionEvent;
            }

            #endregion

            #region Properties

            /// <summary>
            /// Gets or sets Input.
            /// </summary>
            public RemotableEventHandler<OrderEventArgs> Input { get; set; }

            #endregion

            #region Methods

            /// <summary>
            /// The on connection event.
            /// </summary>
            /// <param name="sender">
            /// The sender.
            /// </param>
            /// <param name="args">
            /// The args.
            /// </param>
            private void OnConnectionEvent(object sender, ConnectionEventArgs args)
            {
                // Forward to caller
                this.output(sender, new OrderEventArgs.ConnectionStatus(args));
            }

            /// <summary>
            /// Events triggered by order plugins are sent there first, then forwarded to caller
            /// </summary>
            /// <param name="sender">
            /// </param>
            /// <param name="args">
            /// </param>
            private void OnOrderEvent(object sender, OrderEventArgs args)
            {
                if (args is OrderEventArgs.Status)
                {
                    // Some Status was received
                    var eargs = args as OrderEventArgs.Status;

                    // Front to back
                    Front2Back.AddOrderStatus(eargs.OrderStatus.ToString(), this.order);

                    // Remove us from the pool
                    if (eargs.OrderStatus == EOrderStatus.OutReport)
                    {
                        this.Input -= this.OnOrderEvent;
                        this.plugin.ConnectionEventCB -= this.OnConnectionEvent;
                        lock (this.cells)
                        {
                            this.cells.Remove(this.order.ID);
                        }
                    }
                }
                else if (args is OrderEventArgs.Execution)
                {
                    // Some Execution was received
                    var eargs = args as OrderEventArgs.Execution;

                    // Front to back
                    Front2Back.AddOrderStatus("Execution", this.order);
                    Front2Back.AddOrderExecution(eargs.Quantity, eargs.Price, eargs.Counterparty, this.order);
                }

                // Forward to caller
                this.output(sender, args);
            }

            #endregion
        }
    }
}